package com.amex.uam.dao.impl;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import org.springframework.stereotype.Repository;

import com.amex.uam.dao.MstrDao;
import com.amex.uam.model.MSLogin;
import com.amex.uam.model.Message;
import com.amex.uam.model.SecurityGroupBean;
import com.microstrategy.web.beans.BeanFactory;
import com.microstrategy.web.beans.UserEntitiesBean;
import com.microstrategy.web.beans.UserEntityBean;
import com.microstrategy.web.beans.UserGroupBean;
import com.microstrategy.web.beans.WebBeanException;
import com.microstrategy.web.objects.WebAccessControlEntry;
import com.microstrategy.web.objects.WebAccessControlList;
import com.microstrategy.web.objects.WebFolder;
import com.microstrategy.web.objects.WebIServerSession;
import com.microstrategy.web.objects.WebObjectInfo;
import com.microstrategy.web.objects.WebObjectSource;
import com.microstrategy.web.objects.WebObjectsException;
import com.microstrategy.web.objects.WebObjectsFactory;
import com.microstrategy.web.objects.WebSearch;
import com.microstrategy.web.objects.admin.users.WebStandardLoginInfo;
import com.microstrategy.web.objects.admin.users.WebUser;
import com.microstrategy.web.objects.admin.users.WebUserEntity;
import com.microstrategy.web.objects.admin.users.WebUserGroup;
import com.microstrategy.webapi.EnumDSSXMLAccessEntryType;
import com.microstrategy.webapi.EnumDSSXMLAccessRightFlags;
import com.microstrategy.webapi.EnumDSSXMLApplicationType;
import com.microstrategy.webapi.EnumDSSXMLFolderNames;
import com.microstrategy.webapi.EnumDSSXMLObjectFlags;
import com.microstrategy.webapi.EnumDSSXMLObjectSubTypes;
import com.microstrategy.webapi.EnumDSSXMLObjectTypes;
import com.microstrategy.webapi.EnumDSSXMLSearchDomain;
import com.microstrategy.webapi.EnumDSSXMLSearchFlags;

@Repository("mstrDao")
public class MstrDaoImpl implements MstrDao {

	private enum Action {
		ADD, REMOVE
	}
	
	@Override
	public SecurityGroupBean getGroup(BaseGroup group, WebIServerSession session) {
		return getGroup(group.getName(), session);
	}
	
	@Override
	public SecurityGroupBean getGroup(String groupName, WebIServerSession session) {
		WebObjectSource wos = session.getFactory().getObjectSource();
		WebSearch search = wos.getNewSearchObject();
		search.setSearchFlags(search.getSearchFlags() & ~EnumDSSXMLSearchFlags.DssXmlSearchUsesRecursive );  
		search.setAsync(false );  
		search.types().add(EnumDSSXMLObjectSubTypes.DssXmlSubTypeUserGroup);
		search.setDomain(EnumDSSXMLSearchDomain.DssXmlSearchConfigurationAndAllProjects );
		
		try {
			search.submit();
			WebFolder results = search.getResults();
			String baseId = null; 
			for (int i = 0; i < results.size(); i++) {
				WebObjectInfo obj = results.get(i);
				if (groupName.equalsIgnoreCase(obj.getName())) {
					baseId = obj.getID();
					break;
				}
			}
			if (baseId == null) {
				System.out.println("Warning: group '"+ groupName + "'" + " not found ");
				return null;
			}
			return getGroup(baseId, session, 0);
		
		} catch (WebObjectsException e) {
			e.printStackTrace();
		}
		
		
		return null;
	}
	
	@Override
	public SecurityGroupBean getGroup(String baseId, WebIServerSession session, int level) {
		
		if (baseId == null || session == null) {
			return null;
		}
		
		UserGroupBean usrGrp = (UserGroupBean) BeanFactory.getInstance().newBean("UserGroupBean");
		usrGrp.setSessionInfo(session);
		usrGrp.setObjectID(baseId);
		
		SecurityGroupBean scyGrpBean = new SecurityGroupBean();
		
		try {
			// debug output
			//System.out.println(indent(level) + usrGrp.getObjectName());
			
			scyGrpBean.setGroupName(usrGrp.getObjectName());
			scyGrpBean.setGroupID(usrGrp.getObjectID());
			scyGrpBean.setGroup(usrGrp.isGroup());
			
			UserEntitiesBean children = usrGrp.getChildren();
			for (int i = 0; i < children.size(); i++) {
				UserEntityBean entity = children.get(i);
				if (entity.isGroup()) {
					
					SecurityGroupBean chd = getGroup(entity.getObjectID(), session, level + 1);
					scyGrpBean.addChild(chd);
					
				} /*else if (!scyGrpBean.isContains()) {
					// find user's group
					if (entity.getObjectID().equals(session.getUserInfo().getID())) {
						scyGrpBean.setContains(true);
						
						// debug output
						//System.out.println(indent(level + 1) + " **contains: " + entity.getObjectName());
						
					}
					
				}*/
				
			}
			return scyGrpBean;
		} catch (WebBeanException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	@Override
	public UserGroupBean createGroup(String groupName, WebIServerSession session) {
		return createGroup(groupName, session, true);
	}
	
	@Override
	public UserGroupBean createGroup(String groupName, WebIServerSession session, boolean check) {
		try {
			if (check) {
				WebUserGroup webUserGroup = searchUserGroup(groupName, session);
				if (webUserGroup != null)
					//System.out.println("The group \""+groupName+"\" already exist!");
					return null;
			}
			UserGroupBean group = (UserGroupBean) BeanFactory.getInstance().newBean("UserGroupBean");
			group.setSessionInfo(session);
			group.InitAsNew();
			group.getUserEntityObject().setFullName(groupName);
			
			group.save();
			
			return group;
			
		} catch (WebBeanException e) {
			e.printStackTrace();
			return null;
		}
	}
	
	@Override
	public Message addMemberGroup(String parentGroupName, String childGroupName, WebIServerSession session) {
		List<String> list = new ArrayList<String>();
		list.add(childGroupName);
		return addMemberGroups(parentGroupName, list, session);
	}
	
	@Override
	public Message addMemberGroups(String parentGroupName, Collection<String> childGroupNames, WebIServerSession session){
		//System.out.println("parent name: " + parentGroupName);
		try {
			WebObjectSource source = session.getFactory().getObjectSource();
			WebUserGroup parentGroup = searchUserGroup(parentGroupName, source);
			if (parentGroup == null) {
				return new Message(Message.ERROR, "The parent group \"" + parentGroupName + "\" not found");
			}
			for (Iterator<String> it = childGroupNames.iterator(); it.hasNext();) {
				String childGroupName = it.next();
				WebUserGroup childGroup = searchUserGroup(childGroupName, source);
				if (childGroup == null) {
					System.out.println("Warn: child group \""+ childGroupName + "\" not found");
					continue;
				}
				//System.out.println("  child name: " + childGroupName);
				parentGroup.getMembers().add(childGroup);
			}
			source.save(parentGroup);
			return new Message(Message.OK);
		} catch (WebObjectsException e) {
			e.printStackTrace();
			return new Message(Message.ERROR, e.getMessage());
		}
	}

	@Override
	public Message addParentGroups(String childGroupName, Collection<String> parentGroupNames, WebIServerSession session) {
		throw new RuntimeException("Not yet implemented!!!");
	}
	
	@Override
	public WebIServerSession getAdminSession(MSLogin user){
        
        WebObjectsFactory objectFactory = WebObjectsFactory.getInstance();
		WebIServerSession sessionInfo = objectFactory.getIServerSession();
		sessionInfo.setServerName(user.getServerName());
		sessionInfo.setServerPort(user.getPort());
		sessionInfo.setLogin(user.getUsername());
		sessionInfo.setPassword(user.getPassword());
		sessionInfo.setApplicationType(EnumDSSXMLApplicationType.DssXmlApplicationCustomApp);
    	return sessionInfo;
    }
	
	@Override
	public List<SecurityGroupBean> getUserGroups(String userName, WebIServerSession session) {
		
		List<SecurityGroupBean> list = new ArrayList<SecurityGroupBean>();
		try {
			WebUser user = searchUser(userName, session);
			if (user != null) {
				//System.out.println(user.getAncestorGroups().length);
				user.populate();
				Enumeration elements = user.getParents().elements();
				// System.out.println(parents.size());
				while(elements.hasMoreElements())
				{
					Object obj = elements.nextElement();
					if (obj instanceof WebUserGroup) {
						WebUserGroup group = (WebUserGroup)obj; 
						// System.out.println(group.getID() + " " + group.getName());
						SecurityGroupBean bean = new SecurityGroupBean();
						bean.setGroup(true);
						bean.setGroupName(group.getName());
						bean.setGroupID(group.getID());
						list.add(bean);
					}
				}
				
				// Get all groups
				/*WebUserGroup[] groups = user.getAncestorGroups();
				for (int i = 0; i < groups.length; i++) {
					System.out.println(groups[i]);
				}*/
			}
		} catch (WebObjectsException e) {
			e.printStackTrace();
		}
		return list;
	}

	@Override
	public List<SecurityGroupBean> getUserGroups(WebIServerSession session) {
		List<SecurityGroupBean> list = new ArrayList<SecurityGroupBean>();
		if (session != null) {
			
			try {
				WebUser user = (WebUser)session.getUserInfo();
				user.populate();
				Enumeration elements = user.getParents().elements();
				while(elements.hasMoreElements())
				{
					Object obj = elements.nextElement();
					if (obj instanceof WebUserGroup) {
						WebUserGroup group = (WebUserGroup)obj; 
						SecurityGroupBean bean = new SecurityGroupBean();
						bean.setGroup(true);
						bean.setGroupName(group.getName());
						bean.setGroupID(group.getID());
						list.add(bean);
					}
				}
			} catch (WebObjectsException e) {
				e.printStackTrace();
			}
		}
		return list;
	}

	@Override
	public boolean attachUserToGroup(String userName, String groupName, WebIServerSession managerSession) {
		return handleUserGroup(userName, groupName, managerSession, Action.ADD);
	}
	@Override
	public boolean removeUserFromGroup(String userName, String groupName, WebIServerSession managerSession) {
		return handleUserGroup(userName, groupName, managerSession, Action.REMOVE);
	}
	
	public boolean handleUserGroup(String userName, String groupName, WebIServerSession managerSession, Action action) {
		
		WebObjectSource source = managerSession.getFactory().getObjectSource();
		try {
		    WebUserGroup group = searchUserGroup(groupName, source);
			if (group != null) {
				WebUser user = searchUser(userName, source);
				if (user != null) {
					if (action == Action.ADD) {
						// Add user to group
						group.getMembers().add(user);
					} else {
						// Remove user from group
						group.getMembers().remove(user);
					}
				}
				// Save the group object
				source.save(group);
			}
			return true;
		} catch (WebObjectsException e) {
			e.printStackTrace();
		}

		return false;
	}
	/**
	 * Search User
	 */
	public WebUser searchUser(String userName, WebIServerSession managerSession) {
		WebObjectSource source = managerSession.getFactory().getObjectSource();
		return searchUser(userName, source);
	}
	public WebUser searchUser(String userName, WebObjectSource source) {
		WebSearch userSearch = source.getNewSearchObject();
//		userSearch.setNamePattern(userName);
		userSearch.setAbbreviationPattern(userName);
		userSearch.setAsync(false);
		userSearch.types().add(EnumDSSXMLObjectSubTypes.DssXmlSubTypeUser);
		userSearch.setDomain(EnumDSSXMLSearchDomain.DssXmlSearchConfigurationAndAllProjects);
		WebUser user = (WebUser)performSearch(userSearch);
		return user;
	}
	
	/**
	 * Search User Group
	 */
	@Override
	public WebUserGroup searchUserGroup(String groupName, WebIServerSession managerSession) {
		WebObjectSource source = managerSession.getFactory().getObjectSource();
		return searchUserGroup(groupName, source);
	}
	@Override
	public WebUserGroup searchUserGroup(String groupName, WebObjectSource source) {
		WebSearch groupSearch = source.getNewSearchObject();
		groupSearch.setNamePattern(groupName);
		groupSearch.setAsync(false);
		groupSearch.types().add(EnumDSSXMLObjectSubTypes.DssXmlSubTypeUserGroup);
		groupSearch.setDomain(EnumDSSXMLSearchDomain.DssXmlSearchConfigurationAndAllProjects);
		WebUserGroup group = (WebUserGroup)performSearch(groupSearch);
		return group;
	}
	
	/**
	 * Search Folder
	 */
	@Override
	public WebFolder searchFolder(String folderName, WebIServerSession managerSession) {
		WebObjectSource source = managerSession.getFactory().getObjectSource();
		return (WebFolder) searchObjectByName(folderName, EnumDSSXMLObjectSubTypes.DssXmlSubTypeFolder, source);
		
	}
	
	/****************************************************
	 * Handle ACL
	 ***************************************************/
	@Override
	public boolean grantACLToFolder(String targetName, String trusteeName, WebIServerSession session) {
		WebObjectSource source = session.getFactory().getObjectSource();
		WebFolder target = (WebFolder) searchObjectByName(targetName, EnumDSSXMLObjectSubTypes.DssXmlSubTypeFolder, source);
		WebUser trustee = (WebUser)searchObjectByAbbreviation(trusteeName, EnumDSSXMLObjectSubTypes.DssXmlSubTypeUser, source);
		int rights = ACL_Browse | ACL_Read | ACL_Write | ACL_Delete | ACL_Execute;
		return grantACL(target, trustee, rights, session);
	}
	@Override
	public boolean grantACLToSharedReportsFolder(String userGroupId, String folderId, WebIServerSession session) {

		try {
			WebObjectSource source = session.getFactory().getObjectSource();
			WebUserEntity trustee = (WebUserEntity) source.getObject(userGroupId, EnumDSSXMLObjectTypes.DssXmlTypeUser);
			WebObjectInfo folder = source.getObject(folderId, EnumDSSXMLObjectTypes.DssXmlTypeFolder);
			int rights = 0;
			if(trustee.getName().contains("(Read/Write)")) {
				rights = ACL_Browse | ACL_Delete | ACL_Execute | ACL_Read | ACL_Use | ACL_Write;
			} else if(trustee.getName().contains("(Read Only)")) {
				rights = ACL_Browse| ACL_Execute | ACL_Read | ACL_Use;
			} else {
				rights = ACL_Browse| ACL_Execute | ACL_Read | ACL_Use;
			}
			
			return grantACL(folder, trustee, rights, session);
			
		} catch (WebObjectsException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		}
		return false;
		
	}
	public boolean grantACL(WebObjectInfo target, WebUserEntity trustee, int rights, WebIServerSession session) {
		setACL(target, trustee, rights, false, true, session);
		return setACL(target, trustee, rights, false, false, session);
	}
	public boolean denyACL(WebObjectInfo target, WebUserEntity trustee, int rights, WebIServerSession session) {
		setACL(target, trustee, rights, true, true, session);
		return setACL(target, trustee, rights, true, false, session);
	}
	private boolean setACL(WebObjectInfo target, WebUserEntity trustee, int rights, boolean isAccessDenied, boolean inheritable, WebIServerSession session) {
		
        try {
        	WebObjectSource source = session.getFactory().getObjectSource();
        	
        	target.populate();
        	/*List<WebAccessControlEntry> preDeletedACLs = getAccessControlEntryForTrustee(target, trustee.getName());*/
            WebAccessControlList accessControlList = target.getSecurity().getACL();
            
            WebAccessControlEntry  ace = null;
            /*
            for (WebAccessControlEntry entity : preDeletedACLs) {
            	if (ace == null) {
            		ace = entity; // update this ACE
            	} else {
            		accessControlList.remove(entity);
            	}
			}
            */
            if (ace == null) {// add a new ACE
            	ace = accessControlList.add(EnumDSSXMLAccessEntryType.DssAccessEntryTypeObject);
            	ace.setTrustee(trustee);
            }
            
            ace.setInheritable(inheritable);
        	ace.setRights(rights);
        	ace.setAccessDenied(isAccessDenied);
        	source.setFlags(target.getFlags() & ~EnumDSSXMLObjectFlags.DssXmlObjectDefn);
        	
        	// Note: The object must be saved back to metadata in order to apply the changes
			source.save(target);
			
			return true;
			
		} catch (WebObjectsException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	@Override
	public boolean removeACL(WebObjectInfo target, String trusteeName, WebIServerSession session) {
		WebObjectSource source = session.getFactory().getObjectSource();
		WebUserEntity trustee = searchUserGroup(trusteeName, source);
		if (trustee == null) {
			trustee = searchUser(trusteeName, source);
		}
		if (trustee != null) {
			return removeACL(target, trustee, session);
		}
		return true;
	}
	@Override
	public boolean removeACL(WebObjectInfo target, WebUserEntity trustee, WebIServerSession session) {
		try {
			WebObjectSource source = session.getFactory().getObjectSource();
			target.populate();
			List<WebAccessControlEntry> preDeletedACLs = getAccessControlEntryForTrustee(target, trustee.getName());
			WebAccessControlList accessControlList = target.getSecurity().getACL();
			if (preDeletedACLs.size() > 0) {
				for (WebAccessControlEntry entity : preDeletedACLs) {
					accessControlList.remove(entity);
				}
				source.setFlags(target.getFlags() & ~EnumDSSXMLObjectFlags.DssXmlObjectDefn);
				source.save(target);
			}
			return true;
		} catch (WebObjectsException e) {
			e.printStackTrace();
			return false;
		}
        
	}
	
	 /**
     * Obtains the Access Control Entry for the specified trustee and object
     * @param target WebObjectInfo - The object for which the ACE should be retrieved
     * @param trusteeName String - The trustee for which the ACE should be retrieved
     * @return WebAccessControlEntry list - The ACEs searched for
     */
    private static List<WebAccessControlEntry> getAccessControlEntryForTrustee(WebObjectInfo target, String trusteeName) {
        WebAccessControlEntry ace = null;
        List<WebAccessControlEntry> aceList = new ArrayList<WebAccessControlEntry>();

        //Obtain the ACL from the target object
        WebAccessControlList accessControlList = target.getSecurity().getACL();
        Enumeration controlList = accessControlList.elements();

        //Loop through the elements of the ACL to find the one that matches the trustee we are looking for
        while (controlList.hasMoreElements()) {
            ace = (WebAccessControlEntry) controlList.nextElement();
            if (trusteeName.equals(ace.getTrustee().getName())) {
            	aceList.add(ace);
                break;
            }
        }
        return aceList;
    }
    
    /********************************************
     * Common
     *******************************************/
    private Object searchObjectByName(String name, int objectSubType, WebObjectSource source ){
		WebSearch search = source.getNewSearchObject();
		search.setNamePattern(name);
		search.setAsync(false);
		search.types().add(objectSubType);
		search.setDomain(EnumDSSXMLSearchDomain.DssXmlSearchConfigurationAndAllProjects);
		return performSearch(search);
	}
    private Object searchObjectByAbbreviation(String name, int objectSubType, WebObjectSource source ){
		WebSearch search = source.getNewSearchObject();
		search.setAbbreviation(name);
		search.setAsync(false);
		search.types().add(objectSubType);
		search.setDomain(EnumDSSXMLSearchDomain.DssXmlSearchConfigurationAndAllProjects);
		return performSearch(search);
	}
	
	private Object performSearch(WebSearch search) {
		try {
			search.submit();
			WebFolder folder = search.getResults();
			if (folder.size() > 0) {
				if (folder.size() == 1) {
					return folder.get(0);
				} else {
					System.out.println("Search returns more than 1 object, returning first object");
					return folder.get(0);
				}
			}
		} catch (WebObjectsException ex) {
			System.out.println("Error performing search: " + ex.getMessage());
		}
		return null;
	}
	
	private String indent(int level) {
		StringBuffer buff = new StringBuffer();
		for (int i = 0; i < level; i++) {
			buff.append('\t');
		}
		return buff.toString();
	}
	@Override
	public boolean activateUser(String userId, boolean isActivate, WebIServerSession session) {
		
		try {
			WebObjectSource source = session.getFactory().getObjectSource();
			WebUser user = searchUser(userId, source);
			if (user != null) {
				user.populate();
				user.setEnabled(isActivate);
				source.save(user);
			}
			return true;
		} catch (WebObjectsException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	@Override
	public boolean removeUser(String userId, WebIServerSession session) {
		try {
			WebObjectSource source = session.getFactory().getObjectSource();
			WebUser user = searchUser(userId, source);
			if (user != null) {
				source.deleteObject(user); 
			}
			return true;
		} catch (WebObjectsException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	@Override
	public boolean setPasswordExpirationDate(String userId, Date date, WebIServerSession session) {
		try {
			WebObjectSource source = session.getFactory().getObjectSource();
			WebUser user = searchUser(userId, source);
			user.populate();
			WebStandardLoginInfo loginInfo = user.getStandardLoginInfo();
			loginInfo.setPasswordExpiresAutomatically(true);
			loginInfo.setPasswordExpirationDate(date);
			loginInfo.setPasswordExpirationFrequency(90);
			loginInfo.setStandardAuthAllowed(true);
			source.save(user);
			
			return true;
		} catch (WebObjectsException e) {
			e.printStackTrace();
			return false;
		}
	}
	
	
	@Override
	public WebFolder getTeamSharedReportsFolder(WebIServerSession session, String folderName) {
		return getTeamSharedReportsFolder(session.getFactory().getObjectSource(), folderName);
	}
	
	// path: Project Name/Public Objects/Reports/Team Shared Reports
	@Override
	public WebFolder getTeamSharedReportsFolder(WebObjectSource source, String folderName) {
		
		try {
			String reportFolderId = source.getFolderID(EnumDSSXMLFolderNames.DssXmlFolderNamePublicReports);
			return getSubFolder(source, reportFolderId, folderName);
		} catch (WebObjectsException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public WebFolder getSubFolder(WebIServerSession session, String rootFolderId, String subFolderName) {
		return getSubFolder(session.getFactory().getObjectSource(), rootFolderId, subFolderName);
	}
	
	@Override
	public WebFolder getSubFolder(WebObjectSource source, String rootFolderId, String subFolderName) {
		try {
			WebFolder rootFolder = (WebFolder) source.getObject(rootFolderId, EnumDSSXMLObjectTypes.DssXmlTypeFolder);
			if (rootFolder != null) {
					rootFolder.populate();
					for (int i = 0; i < rootFolder.size(); i++) {
						WebObjectInfo object = rootFolder.get(i);
						if (object.getSubType() == EnumDSSXMLObjectSubTypes.DssXmlSubTypeFolder) {
							if (subFolderName.equals(object.getName())) {
								return (WebFolder) object;
							}
						}
					}
			}
		} catch (WebObjectsException e) {
			e.printStackTrace();
		}
		return null;
	}
	@Override
	public WebFolder createFolder(WebFolder parentFolder, String folderName, WebIServerSession session) {
		return createFolder(parentFolder, folderName, session.getFactory().getObjectSource());
	}
	@Override
	public WebFolder createFolder(WebFolder parentFolder, String folderName, WebObjectSource source) {
		WebFolder newFolder = (WebFolder) source.getNewObject(EnumDSSXMLObjectTypes.DssXmlTypeFolder, folderName);
		try {
			source.saveAs(newFolder, newFolder.getName(), parentFolder, true);
			return newFolder;
		} catch (WebObjectsException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
}
